package cn.git.agent.collects;

import cn.git.agent.basic.BasicMethodBuildSrc;
import cn.git.agent.basic.BasicTransformInfo;
import cn.git.agent.basic.ControllerTransformInfo;
import cn.git.agent.init.AbstractCollect;
import cn.git.agent.init.BasicCollect;
import cn.git.agent.util.AgentSrcBuilderUtil;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @description: web层controller基本信息采集
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-03-18 04:04:16
 */
@Data
@NoArgsConstructor
public class ControllerCollect extends AbstractCollect implements BasicCollect {

    /**
     * 插入内容信息
     */
    private static final String BEGIN_SRC;
    private static final String END_SRC;
    private static final String ERROR_SRC;

    /**
     * 内部类单例加载
     */
    private static class ControllerInnerCollect {
        private static final ControllerCollect INSTANCE = new ControllerCollect();
    }

    /**
     * 获取实例
     *
     * @return ControllerCollect
     */
    public static ControllerCollect getInstance() {
        return ControllerInnerCollect.INSTANCE;
    }

    /**
     * 构造修改方法插入内容
     * beginSrc
     * errorSrc
     * endSrc
     */
    static {
        StringBuilder strBuilder = new StringBuilder();
        // 开始插入内容
        strBuilder.append(" cn.git.agent.collects.ControllerCollect instance = ");
        strBuilder.append(" cn.git.agent.collects.ControllerCollect.getInstance(); ");
        strBuilder.append(" cn.git.agent.basic.ControllerTransformInfo transInfo = instance.customModifyBegin(\"%s\",\"%s\",\"%s\"); ");
        BEGIN_SRC = strBuilder.toString();
        strBuilder.setLength(0);
        // 结束插入内容
        strBuilder.append(" instance.modifyEnd(transInfo); ");
        END_SRC = strBuilder.toString();
        // 异常错误插入内容
        strBuilder.append(" instance.modifyError(transInfo,e); ");
        ERROR_SRC = strBuilder.toString();
    }

    /**
     * 判定是否为采集目录
     *
     * @param className     类名称
     * @param loader        类加载器
     * @param ctclass       ctclass
     * @return 是否采集类
     */
    @Override
    public boolean isTarget(String className, ClassLoader loader, CtClass ctclass) {
        // 名称校验
        if (!className.toLowerCase().endsWith("controller")) {
            return false;
        }
        // 校验结果
        boolean checkResult = false;

        // 注解校验
        try {
            for (Object annotation : ctclass.getAnnotations()) {
                checkResult = checkPathAnnotation(annotation.toString());
                if (checkResult) {
                    break;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return checkResult;
    }

    /**
     * 请求注解校验
     * @param annotationName
     * @return
     */
    public boolean checkPathAnnotation(String annotationName) {
        boolean result = false;
        if (annotationName.startsWith("@org.springframework.stereotype.Controller")) {
            result = true;
        } else if (annotationName.startsWith("@org.springframework.web.bind.annotation.RestController")) {
            result = true;
        } else if (annotationName.startsWith("@org.springframework.web.bind.annotation.RequestMapping")) {
            result = true;
        }
        return result;
    }

    /**
     * 转换给定的类文件，动态地向其方法中插入代码。
     *
     * @param loader 用于加载类的类加载器。
     * @param className 待转换的类名称。
     * @param classfileBuffer 类文件的字节码。
     * @param ctclass CtClass对象，提供对类文件的高级访问。
     * @return 转换后的类文件的字节码。
     * @throws Exception 如果转换过程中发生错误。
     */
    public byte[] transform(ClassLoader loader,
                            String className,
                            byte[] classfileBuffer,
                            CtClass ctclass) throws Exception {
        // 初始化代码构建工具
        AgentSrcBuilderUtil agentSrcBuilderUtil = new AgentSrcBuilderUtil(className, loader, ctclass);
        // 收集类的所有方法
        List<CtMethod[]> classList = new ArrayList<>();
        // 获取类实现的接口，并收集其方法
        String[] interfaces = ctclass.getClassFile().getInterfaces();
        for (String anInterface : interfaces) {
            CtClass c = ctclass.getClassPool().get(anInterface);
            classList.add(c.getDeclaredMethods());
        }
        CtMethod[] methods = ctclass.getDeclaredMethods();
        Object[] annotations = ctclass.getAnnotations();
        String prefixUri = null;
        // 遍历注解，寻找@RequestMapping注解，提取URI前缀
        for (Object annotation : annotations) {
            if (annotation.toString().startsWith("@org.springframework.web.bind.annotation.RequestMapping")) {
                prefixUri = getAnnotationValue("value", annotation.toString());
            }
        }
        // 遍历所有方法，为每个符合条件的方法插入代码
        for (CtMethod method : methods) {
            AtomicReference<String> requestUrl = new AtomicReference<>("");
            // 过滤掉非公共方法、静态方法和本地方法
            if (!Modifier.isPublic(method.getModifiers())) {
                continue;
            }
            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }
            if (Modifier.isNative(method.getModifiers())) {
                continue;
            }
            // 遍历方法注解，寻找GetMapping注解，提取URI后缀
            String suffixUri = null;
            for (Object annotation : method.getAnnotations()) {
                if (annotation.toString().startsWith("@org.springframework.web.bind.annotation.GetMapping")) {
                    suffixUri = getAnnotationValue("value", annotation.toString());
                }
            }
            // 设置插入代码的内容
            BasicMethodBuildSrc basicMethodBuildSrc = new BasicMethodBuildSrc();
            // 构建插入代码的开始部分，目前仅支持GetMapping
            basicMethodBuildSrc.setBeginInsertSrc(String.format(BEGIN_SRC, className, method.getName(), prefixUri.concat(suffixUri)));
            basicMethodBuildSrc.setEndInsertSrc(END_SRC);
            basicMethodBuildSrc.setErrorInsertSrc(ERROR_SRC);
            // 更新目标方法，插入代码
            agentSrcBuilderUtil.updateTargetMethod(method, basicMethodBuildSrc);
        }
        // 生成并返回转换后的类文件字节码
        return agentSrcBuilderUtil.toNewBytecode();
    }


    /**
     * 获取链接
     * @param key
     * @param annotationDesc
     * @return
     */
    public static String getAnnotationValue(String key, String annotationDesc) {
        String regex = String.format("value=\\{\".*\"\\}");
        Pattern r = Pattern.compile(regex);
        Matcher matcher = r.matcher(annotationDesc);
        if (matcher.find()) {
            return matcher.group().substring(key.length() + 3, matcher.group().length() - 2);
        }
        return null;
    }

    /**
     * 对目标方法进行改造
     *
     * @param className
     * @param methodName
     * @return
     */
    @Override
    public BasicTransformInfo modifyBegin(String className, String methodName) {
        // 设置参数信息
        ControllerTransformInfo controllerTransformInfo = new ControllerTransformInfo(super.modifyBegin(className, methodName));
        controllerTransformInfo.setControlName(className);
        controllerTransformInfo.setUrlAddress(methodName);
        controllerTransformInfo.setMethodName(methodName);
        controllerTransformInfo.setLogType("ControllerWeb");
        System.out.println("探针内容开始执行modifyBegin,设置参数为 : ".concat(controllerTransformInfo.toString()));
        return controllerTransformInfo;
    }

    /**
     * 对目标方法进行改造,自定义方法
     *
     * @param className
     * @param methodName
     * @param urlAddress
     * @return
     */
    public BasicTransformInfo customModifyBegin(String className, String methodName, String urlAddress) {
        // 设置参数信息
        ControllerTransformInfo controllerTransformInfo = (ControllerTransformInfo) modifyBegin(className, methodName);
        controllerTransformInfo.setUrlAddress(urlAddress);
        return controllerTransformInfo;
    }

    @Override
    public void modifyEnd(BasicTransformInfo basicTransformInfo) {
        System.out.println("探针内容开始执行modifyEnd");
        super.modifyEnd(basicTransformInfo);
    }
}
